package com.aruba.socketlibrary.protocol;

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Handler;

import com.aruba.socketlibrary.config.SerialportConfig;
import com.aruba.socketlibrary.protocol.SendManager.SendDataManager;
import com.aruba.socketlibrary.protocol.SendManager.SendResponse;
import com.aruba.socketlibrary.protocol.SocketManager.DataCallBack;
import com.aruba.socketlibrary.utils.Tools;
import com.socks.library.KLog;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.security.InvalidParameterException;

/**
 * Created by aruba on 2017/9/26.
 */

public class SocketHelper {
    private static final String tag = SocketHelper.class.getSimpleName();
    private boolean _isOpen = false;
    private DataCallBack dataCallBack;
    private String mIP;
    private int mPort;
    private InetSocketAddress mAddr;
    private boolean mConnected = false;
    private RecvThread recvThread;
    private Context mContext;
    private Handler handler;
    private Runnable sendRunable;
    private SendDataManager sendDataManager;

    //----------------------------------------------------
    public SocketHelper(Context context, String sip, int iPort) {
        this.mIP = sip;
        this.mPort = iPort;
        this.mContext = context.getApplicationContext();
        sendDataManager = new SendDataManager();
        handler = new Handler(SendDataManager.getWorkerLooper(sip));
        sendRunable = new Runnable() {
            private long time;
            private int timeout = 10000;
            private int resendCount = 0;

            @Override
            public void run() {

                if (sendDataManager.getDataTask().size() > 0) {
                    SendResponse sendResponse = sendDataManager.getDataTask().remove();
                    resendCount = 0;
                    realSend(sendResponse);
                } else {
                    sendDataManager.isLock = false;
                }

                if (handler != null)
                    handler.postDelayed(this, 10);
            }

            private void realSend(SendResponse sendResponse) {
                send(sendResponse.data);

                if (sendResponse.isReply || !mConnected) {
                    return;
                }

                sendDataManager.isLock = true;

                time = System.currentTimeMillis();
                while (sendDataManager.isLock && _isOpen) {
                    if (System.currentTimeMillis() - time > timeout) {
                        resendCount++;
                        if (resendCount < 4) {
                            realSend(sendResponse);//重发
                        }

                        break;
                    }
                }
            }
        };
    }


    public void open() throws SecurityException, IOException, InvalidParameterException {
        if (_isOpen) {
            return;//已经打开
        }
        _isOpen = true;
        recvThread = new RecvThread();
        recvThread.start();

        handler.postDelayed(sendRunable, 10);
    }

    public void close() {
        _isOpen = false;
        if (mConnected && recvThread != null) {
            recvThread.close();
        }

        if (handler != null) {
            if (sendRunable != null) {
                handler.removeCallbacks(sendRunable);
            }

            handler = null;
        }

        sendDataManager.destory(mIP);
    }

    /**
     * 数据接收线程
     */
    public class RecvThread extends Thread {

        DataInputStream in = null;
        DataOutputStream dos = null;
        Socket socket = null;
        int timeout = 15000;
        int connectFailTime = 1;

        private boolean connect() {
            mAddr = new InetSocketAddress(mIP, mPort);
            KLog.i(tag, "connect %s:%d ...", mIP, mPort);
            boolean bl;

            try {
                socket = new Socket();
                socket.connect(mAddr, timeout);
                // 获取输出流
                dos = new DataOutputStream(socket.getOutputStream());
                // 获取输入流
                in = new DataInputStream(socket.getInputStream());

                mConnected = true;
                KLog.i(tag, " ok!");
                bl = true;
            } catch (Exception ex) {
                bl = false;
                KLog.i(tag, "connect %s:%d failed!", mIP, mPort);
                KLog.i(tag, "connect " + connectFailTime * 10 + "seconds  ago!");
            }
            return bl;
        }

        @Override
        public void run() {
            KLog.i(tag, "RecvThread Run");
            byte[] temp = new byte[1024];
            int recvLen;

            while (_isOpen) {
                try {
                    if (!mConnected) {
                        if (!connect()) {
                            sleep(connectFailTime * 10000);
                            continue;
                        }
                    }

                    // 循环读取数据
                    recvLen = in.read(temp, 0, temp.length);
                    if (recvLen <= 0) {
                        KLog.i(tag, "disconnect!");
                        close();
                        sleep(10 * 1000);
                        continue;
                    }

                    if (recvLen > 0) {
                        sendDataManager.isLock = false;
                        if (dataCallBack != null) {
                            KLog.i(tag, "read：" + Tools.byteToHexString(temp, recvLen));
                            dataCallBack.callBack(temp, recvLen);
                        }
                    } else {
                        if (dataCallBack != null) {
                            dataCallBack.emptyCallBack();
                        }
                    }

                } catch (Exception e) {
                    KLog.e(tag, e);
                    KLog.e(tag, "closesocket!");
                    close();
                    try {
                        super.sleep(5000);
                    } catch (InterruptedException e1) {
                        KLog.e(tag, e1);
                    }
                }
            }
            KLog.i(tag, "recv Thread exit!");
            close();
        }

        public boolean write(byte[] buffer) {
            if (socket == null) {
                KLog.i(tag, "socket == null");
                return false;
            }

            if (!socket.isConnected()) {
                KLog.i(tag, "!socket.isConnected()");
                return false;
            }

            return write(buffer, 0, buffer.length);
        }

        private boolean write(byte[] buffer, int offset, int length) {
            try {
                KLog.i(tag, "write:%d", length);
                //				KLog.buffer("send","send:", buffer);
                dos.write(buffer, offset, length);
                return true;
            } catch (Exception e) {
                KLog.e(tag, "write failed!");
                return false;
            }
        }

        private void close() {
            KLog.i(tag, "close");
            if (!mConnected) {
                return;
            }
            mConnected = false;
            if (dos != null) {
                try {
                    dos.flush();
                    dos.close();
                } catch (IOException e) {
                    KLog.e(tag, e);
                }
                dos = null;
            }
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    KLog.e(tag, e);
                }
                in = null;
            }

            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    KLog.e(tag, e);
                }
                socket = null;
            }
        }
    }

    public boolean isConnected() {
        return mConnected;
    }

    public void setDataManger(DataCallBack dataCallBack) {
        this.dataCallBack = dataCallBack;
        dataCallBack.bind(this);
    }

    @SuppressLint("DefaultLocale")
    public static String byteToHexString(byte[] bArray, final int size) {
        StringBuffer sb = new StringBuffer(size + size / 2);
        String sTemp;
        for (int i = 0; i < size; i++) {
            sTemp = Integer.toHexString(0xFF & bArray[i]);
            if (sTemp.length() < 2)
                sb.append(0);
            sb.append(sTemp.toUpperCase());
            sb.append(' ');
        }
        return sb.toString();
    }

    public boolean send(byte[] bOutArray) {
        KLog.i("SocketHelper", "发送的数据是：" + byteToHexString(bOutArray, bOutArray.length));
        if (recvThread == null) {
            return false;
        }
        return recvThread.write(bOutArray);
    }

    public synchronized boolean send(SendResponse sendResponse) {
        byte[] data = sendResponse.data;
        int memlen = data.length + SerialportConfig.no_use_length;//头+长度+设备类型+厂商标识 +命令字+校验位+结束位 

        byte[] msg = new byte[memlen];
        // 起始位
        msg[0] = (byte) 0x55;
        msg[1] = (byte) 0xAA;

        int len = memlen - 7;
        // 包长度(数据+命令字)	
        msg[2] = (byte) (len / 256);
        msg[3] = (byte) (len % 256);

        // 设备类型 。ISU发给出租屏的消息，设备类型为0x10；出租屏发给ISU的消息，设备类型为0x00。  
        msg[4] = 0x00;
        // 厂商标识 
        msg[5] = 0x01;
        //命令字 ，ISU发给出租屏的命令字第一个字节为0xFF，出租屏发给ISU的命令字第一个字节为0x10。
        msg[6] = 0x10;
        msg[7] = sendResponse.commandId;

        //数据区 
        if (data.length > 0) {
            System.arraycopy(data, 0, msg, 8, data.length);
        }

        //校验码
        int __xor = (byte) Tools.makeCheckSum(msg);//从包长度到数据区数据的异或运算和
        msg[msg.length - 3] = (byte) (__xor);
        //结束位 
        msg[msg.length - 2] = 0x55;
        msg[msg.length - 1] = (byte) 0xAA;

        sendResponse.data = msg;
        sendDataManager.addDataTask(sendResponse);
        return true;
    }

    public synchronized boolean sendCustom(SendResponse sendResponse) {
        sendDataManager.addDataTask(sendResponse);
        return true;
    }

}
